module SvmToolkit

  # The Evaluator classes provides some classes and methods to construct 
  # classes for evaluating the performance of a model against a dataset.  
  # Different evaluators measure different kinds of performance.  
  #
  # Evaluator classes are accessed by name, with an optional positive label name.
  # For example:
  #
  #   Evaluator::OverallAccuracy # => class evaluates overall accuracy
  #   Evaluator::ClassPrecision(label) # => class evaluates precision for class "label"
  # 
  # Evaluators are wrapped around confusion matrices, outputting the required 
  # statistical measure, and support the following methods:
  #
  # add_result(actual, prediction):: called to add information about each instance
  #                                  when testing a model.
  # value:: retrieves the appropriate measure of performance, based on the class name.
  # to_s:: returns a string naming the evaluator and giving its value.
  #
  class Evaluator

    # Defines an Evaluator returning the value of precision for given class
    # label.
    #
    def Evaluator.ClassPrecision label
      Class.new(Evaluator) do 
        @@label = label

        # Returns the precision.
        #
        def value
          @cm.precision(@@label)
        end

        def to_s # :nodoc:
          "Precision for label #{@@label}: #{value}"
        end
      end
    end

    # Defines an Evaluator returning the value of recall for given class
    # label.
    #
    def Evaluator.ClassRecall label
      Class.new(Evaluator) do 
        @@label = label

        def value # :nodoc:
          @cm.recall(@@label)
        end

        def to_s # :nodoc:
          "Recall for label #{@@label}: #{value}"
        end
      end
    end

    # Defines an Evaluator returning the value of the F-measure for given class
    # label.
    #
    def Evaluator.FMeasure label
      Class.new(Evaluator) do 
        @@label = label

        def value # :nodoc:
          @cm.f_measure(@@label)
        end

        def to_s # :nodoc:
          "F-measure for label #{@@label}: #{value}"
        end
      end
    end

    # Defines an Evaluator returning the value of Cohen's Kappa statistics for
    # given class label.
    #
    def Evaluator.Kappa label
      Class.new(Evaluator) do 
        @@label = label

        def value # :nodoc:
          @cm.kappa(@@label)
        end

        def to_s # :nodoc:
          "Kappa for label #{@@label}: #{value}"
        end
      end
    end

    # Defines an Evaluator returning the value of the Matthews Correlation
    # Coefficient for given class label.
    #
    def Evaluator.MatthewsCorrelationCoefficient label
      Class.new(Evaluator) do 
        @@label = label

        def value # :nodoc:
          @cm.matthews_correlation(@@label)
        end

        def to_s # :nodoc:
          "Matthews correlation coefficient: #{value}"
        end
      end
    end

    # Creates a new Evaluator, with a confusion matrix to store results.
    #
    def initialize
      @cm = ConfusionMatrix.new
    end

    # Adds result to the underlying confusion matrix.
    #
    def add_result(actual, prediction)
      @cm.add_for(actual, prediction)
    end

    # This object is better than given object, if the given object is an 
    # instance of nil, or the value of this object is better.
    #
    def better_than? other
      other.nil? or self.value > other.value
    end

    # Prints the confusion matrix.
    #
    def display
      puts @cm
    end
  end

  # Defines an Evaluator returning the value of overall accuracy.
  #
  class OverallAccuracy < Evaluator
    # Returns the overall accuracy, as a percentage.
    #
    def value
      100 * @cm.overall_accuracy
    end

    # Returns a string naming this evaluator and giving its value.
    #
    def to_s
      "Overall accuracy: #{value}%"
    end
  end

  # Defines an Evaluator returning the value of geometric mean.
  #
  class GeometricMean < Evaluator
    # Returns the geometric mean.
    #
    def value
      @cm.geometric_mean
    end

    # Returns a string naming this evaluator and giving its value.
    #
    def to_s
      "Geometric mean: #{value}"
    end
  end

end

